{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 类 —— Python 的实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "既然已经在不碰代码的情况下，把 OOP 中的主要概念梳理清楚了，以下的行文中，那些概念就直接用英文罢，省得理解上还得再绕个弯……"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining Class"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Class 使用 `class` 关键字进行定义。\n",
    "\n",
    "与函数定义不同的地方在于，Class 接收参数不是在 `class Classname():` 的括号里完成 —— 那个圆括号有另外的用处。\n",
    "\n",
    "让我们先看看代码，而后再逐一解释："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Clay'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "2019"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "<bound method Golem.say_hi of <__main__.Golem object at 0x10430e7b8>>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hi!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "__main__.Golem"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "str"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "int"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "method"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "method"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "        \n",
    "g = Golem('Clay')\n",
    "g.name\n",
    "g.built_year\n",
    "g.say_hi\n",
    "g.say_hi()\n",
    "type(g)\n",
    "type(g.name)\n",
    "type(g.built_year)\n",
    "type(g.__init__)\n",
    "type(g.say_hi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上，我们创建了一个 Class:\n",
    "\n",
    "```python\n",
    "class Golem:\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "```\n",
    "\n",
    "其中定义了当我们根据这个 Class 创建一个实例的时候，那个 Object 的初始化过程，即 `__init__()` 函数 —— 又由于这个函数是在 Class 中定义的，我们称它为 Class 的一个 Method。\n",
    "\n",
    "这里的 `self` 就是个变量，跟程序中其它变量的区别在于，它是一个系统默认可以识别的变量，用来指代将来用这个 Class 创建的 Instance。\n",
    "\n",
    "比如，我们创建了 Golem 这个 Class 的一个 Instance，`g = Golem('Clay')` 之后，我们写 `g.name`，那么解析器就去找 `g` 这个实例所在的 Scope 里有没有 `self.name`……\n",
    "\n",
    "注意：`self` 这个变量的定义，是在 `def __init__(self, ...)` 这一句里完成的。对于这个变量的名称取名没有强制要求，你实际上可以随便用什么名字，很多 C 程序员会习惯于将这个变量命名为 `this` —— 但根据惯例，你最好还是只用 `self` 这个变量名，省得给别人造成误会。\n",
    "\n",
    "在 Class 的代码中，如果定义了 `__init__()` 函数，那么系统就会将它当作“ Instance 在被创建后初始化的函数”。这个函数名称是强制指定的，初始化函数必须使用这个名称；注意 `init` 两端各有两个下划线 `_`。\n",
    "\n",
    "当我们用 `g = Golem('Clay')` 这一句创建了一个 Golem 的 Instance 的时候，以下一连串的事情发生了：\n",
    "\n",
    "> * `g` 从此之后就是一个根据 Golem 这个 Class 创建的 Instance，对使用者来说，它就是个 Object；\n",
    "> * 因为 Golem 这个 Class 的代码中有 `__init__()`，所以，当 `g` 被创建的时候，`g` 就需要被初始化……\n",
    "> * 在 `g` 所在的变量目录中，出现了一个叫做 `self` 的用来指代 `g` 本身的变量；\n",
    "> * self.name 接收了一个参数，`'Clay'`，并将其保存了下来；\n",
    "> * 生成了一个叫做 `self.built_year` 的变量，其中保存的是 `g` 这个 Object 被创建时的年份……\n",
    "\n",
    "对了，Golem 和 Robot 一样，都是机器人的意思；Golem 的本义来自于犹太神话，一个被赋予了生命的泥人……"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inheritance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们刚刚创建了一个 Golem Class，如果我们想用它 Inherite 一个新的 Class，比如，`Running_Golem`，一个能跑的机器人，那就像以下的代码那样做 —— 注意 `class Running_Golem` 之后的圆括号："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bound method Running_Golem.run of <__main__.Running_Golem object at 0x1068b37b8>>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Can't you see? I'm running...\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Clay'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "2019"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hi!\n"
     ]
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "\n",
    "class Running_Golem(Golem):      # 刚刚就说，这个圆括号另有用途……\n",
    "    \n",
    "    def run(self):\n",
    "        print(\"Can't you see? I'm running...\")\n",
    "\n",
    "rg = Running_Golem('Clay')\n",
    "\n",
    "rg.run\n",
    "rg.run()\n",
    "rg.name\n",
    "rg.built_year\n",
    "rg.say_hi()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如此这般，我们根据 Golem 这个 Class 创造了一个 Subclass —— `Running_Golem`，既然它是 Golem 的 Inheritance，那么 Golem 有的 Attributes 和 Methods 它都有，并且还多了一个 Method —— `self.run`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overrides"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当我们创建一个 Inherited Class 的时候，可以重写（Overriding）Parent Class 中的 Methods。比如这样："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bound method runningGolem.run of <__main__.runningGolem object at 0x1068c8128>>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Can't you see? I'm running...\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Clay'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "2019"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hey! Nice day, Huh?\n"
     ]
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "\n",
    "class runningGolem(Golem):\n",
    "    \n",
    "    def run(self):\n",
    "        print(\"Can't you see? I'm running...\")\n",
    "    \n",
    "    def say_hi(self):                            # 不再使用 Parent Class 中的定义，而是新的……\n",
    "        print('Hey! Nice day, Huh?')\n",
    "\n",
    "rg = runningGolem('Clay')\n",
    "rg.run\n",
    "rg.run()\n",
    "rg.name\n",
    "rg.built_year\n",
    "rg.say_hi()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspecting A Class"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当我们作为用户想了解一个 Class 的 Interface，即，它的 Attributes 和 Methods 的时候，常用的有三种方式：\n",
    "\n",
    "```python\n",
    "1. help(object)\n",
    "2. dir(object)\n",
    "3. object.__dict__\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on runningGolem in module __main__ object:\n",
      "\n",
      "class runningGolem(Golem)\n",
      " |  runningGolem(name=None)\n",
      " |  \n",
      " |  Method resolution order:\n",
      " |      runningGolem\n",
      " |      Golem\n",
      " |      builtins.object\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  run(self)\n",
      " |  \n",
      " |  say_hi(self)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Methods inherited from Golem:\n",
      " |  \n",
      " |  __init__(self, name=None)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors inherited from Golem:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['__class__',\n",
       " '__delattr__',\n",
       " '__dict__',\n",
       " '__dir__',\n",
       " '__doc__',\n",
       " '__eq__',\n",
       " '__format__',\n",
       " '__ge__',\n",
       " '__getattribute__',\n",
       " '__gt__',\n",
       " '__hash__',\n",
       " '__init__',\n",
       " '__init_subclass__',\n",
       " '__le__',\n",
       " '__lt__',\n",
       " '__module__',\n",
       " '__ne__',\n",
       " '__new__',\n",
       " '__reduce__',\n",
       " '__reduce_ex__',\n",
       " '__repr__',\n",
       " '__setattr__',\n",
       " '__sizeof__',\n",
       " '__str__',\n",
       " '__subclasshook__',\n",
       " '__weakref__',\n",
       " 'built_year',\n",
       " 'name',\n",
       " 'run',\n",
       " 'say_hi']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "{'name': 'Clay', 'built_year': 2019}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "\n",
    "class runningGolem(Golem):\n",
    "    \n",
    "    def run(self):\n",
    "        print('Can\\'t you see? I\\'m running...')\n",
    "    \n",
    "    def say_hi(self):                            # 不再使用 Parent Class 中的定义，而是新的……\n",
    "        print('Hey! Nice day, Huh?')\n",
    "\n",
    "rg = runningGolem('Clay')\n",
    "help(rg)\n",
    "dir(rg)\n",
    "rg.__dict__\n",
    "hasattr(rg, 'built_year')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Scope"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "每个变量都属于某一个 **Scope**（变量的作用域），在同一个 Scope 中，变量可以被引用被操作…… 这么说非常抽象，难以理解 —— 只能通过例子说明。\n",
    "\n",
    "我们先给 Golem 这个 Class 增加一点功能 —— 我们需要随时知道究竟有多少个 Golem 处于活跃状态…… 也因此顺带给 Golem 加上一个 Method：`cease()` —— 哈！机器人么，想关掉它，说关掉它，就能关掉它；\n",
    "\n",
    "另外，我们还要给机器人设置个使用年限，比如 10 年；\n",
    "\n",
    "…… 而外部会每隔一段时间，用 `Golem.is_active()` 去检查所有的机器人，所以，不需要外部额外操作，到了年头，它应该能关掉自己。—— 当然，又由于以下代码是简化书写的，核心目的是为了讲解 Scope，所以并没有专门写模拟 10 年后某些机器人自动关闭的情形……\n",
    "\n",
    "在运行以下代码之前，需要先介绍三个 Python 的内建函数：\n",
    "\n",
    "> * `hasattr(object, attr)` 查询这个 `object` 中有没有这个 `attr`，返回布尔值\n",
    "> * `getattr(object, attr)` 获取这个 `object` 中这个 `attr` 的值\n",
    "> * `setattr(object, attr, value)` 将这个 `object` 中的 `attr` 值设置为 `value`\n",
    "\n",
    "现在的你，应该一眼望过去，就已经能掌握这三个内建函数的用法 —— 还记得之前的你吗？眼睁睁看着，那些字母放在那里对你来说没任何意义…… 这才多久啊！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "11"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    population = 0\n",
    "    __life_span = 10\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "        self.__active = True\n",
    "        Golem.population += 1          # 执行一遍之后，试试把这句改成 population += 1\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "    \n",
    "    def cease(self):\n",
    "        self.__active = False\n",
    "        Golem.population -= 1\n",
    "    \n",
    "    def is_active(self):\n",
    "        if datetime.date.today().year - self.built_year >= Golem.__life_span:\n",
    "            self.cease()\n",
    "        return self.__active\n",
    "\n",
    "g = Golem()\n",
    "hasattr(Golem, 'population')      # True\n",
    "hasattr(g, 'population')          # True\n",
    "hasattr(Golem, '__life_span')     # False\n",
    "hasattr(g, '__life_span')         # False\n",
    "hasattr(g, '__active')            # False\n",
    "Golem.population                  # 1\n",
    "setattr(Golem, 'population', 10) \n",
    "Golem.population                  # 10\n",
    "x = Golem()\n",
    "Golem.population                  # 11\n",
    "x.cease()\n",
    "Golem.population                  # 10\n",
    "getattr(g, 'population')          # 10\n",
    "g.is_active()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果你试过把第 13 行的 `Golem.population += 1` 改成 `population += 1`，你会被如下信息提醒：\n",
    "\n",
    "```python\n",
    "     12         self.__active = True\n",
    "---> 13         population += 1\n",
    "UnboundLocalError: local variable 'population' referenced before assignment\n",
    "```\n",
    "—— 本地变量 `population` 尚未赋值，就已经提前被引用…… 为什么会这样呢？因为在你所创建 `g` 之后，马上执行的是 `__init()__` 这个初始化函数，而 `population` 是在这个函数之外定义的……\n",
    "\n",
    "如果你足够细心，你会发现这个版本中，有些变量前面有两个下划线 `__`，比如，`__life_span` 和 `self.__active`。这是 Python 的定义，变量名前面加上一个以上下划线（Underscore）`_` 的话，那么该变量是 “私有变量”（Private Variables），不能被外部引用。而按照 Python 的惯例，我们会使用两个下划线起始，去命名私有变量，如：`__life_span`。你可以回去试试，把所有的 `__life_span` 改成 `_life_span`（即，变量名开头只有一个 `_`，那么，`hasattr(Golem, '_life_span')` 和 `hasattr(g, '_life_span')` 的返回值就都变成了 `True`。\n",
    "\n",
    "看看下面的图示，理解起来更为直观一些，其中每个方框代表一个 Scope：\n",
    "\n",
    "![](images/class-variables-scope.png)\n",
    "\n",
    "整个代码启动之后，总计有 4 个 Scopes 如图所示：\n",
    "\n",
    "> * ① `class Golem` 之外；\n",
    "> * ② `class Golem` 之内；\n",
    "> * ③ `__init__(self, name=None)` 之内；\n",
    "> * ④ `cease(self)` 之内；\n",
    "\n",
    "在 Scope ① 中，可以引用 `Golem.population`，在生成一个 Golem 的实例 `g` 之后，也可以引用 `g.population`；但 `Golem.__life_span` 和 `g.__active` 在 Scope ① 是不存在的；\n",
    "\n",
    "在 Scope ② 中，存在两个变量，`population` 和 `__life_span`；而 `__life_span` 是 Private（私有变量，因为它的变量名中前两个字符是下划线 `__`；于是，在 Scope ① 中，不存在 `Golem.__life_span` —— `hasattr(Golem, '__life_span')` 的值为 `False`；\n",
    "\n",
    "在 Scope ③ 中和 Scope ④ 中，由于都给它们传递了 `self` 这个参数，于是，在这两个 Scope 里，都可以引用 `self.xxx`，比如 `self.population`，比如 `self.__life_span`；\n",
    "\n",
    "在 Scope ③ 中，`population` 是不存在的，如果需要引用这个值，可以用 `Golem.population`，也可以用 `self.population`。同样的道理，在 Scope ③ 中 `__life_span` 也不存在，如果想用这个值，可以用 `Golem.__life_span` 或者 `self.__life_span`；\n",
    "\n",
    "Scope ④ 与 Scope ③ 平行存在。所以在这里，`population` 和 `__life_span` 也同样并不存在。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**补充**\n",
    "\n",
    "在本例子中，在 `__init__(self, name=None)` 函数中 `self.population` 和 `Golem.population` 都可以使用，但使用效果是不一样的：\n",
    "> * `self.population` 总是去读取 `Golem` 类中 `population` 的初始值，即使后面通过 `setattr(Golem, 'population', 10)` 更改 `population` 的值后，`self.population` 的值仍为 `0`，但 `Golem.population` 值则为 `10`，你可以自己动手尝试一下。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "toc-hr-collapsed": true
   },
   "source": [
    "## Encapsulation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "到目前为止，Golem 这个 Class 看起来不错，但有个问题，它里面的数据，外面是可以随便改的 —— 虽然，我们已经通过给变量 life_span 前面加上两个下划线，变成 `__life_span`，使其成为私有变量，外部不能触达（你不能引用 `Golem.__life_span`），可 Golem.population 就不一样，外面随时可以引用，还可以随时修改它，只需要写上一句：\n",
    "\n",
    "```python\n",
    "Golem.population = 1000000\n",
    "```\n",
    "\n",
    "我们干脆把 `population` 这个变量也改成私有的罢：`__population`，而后需要从外界查看这个变量的话，就在 Class 里面写个函数，返回那个值好了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bound method Golem.population of <__main__.Golem object at 0x1068da160>>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    __population = 0\n",
    "    __life_span = 10\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "        self.__active = True\n",
    "        Golem.__population += 1\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "    \n",
    "    def cease(self):\n",
    "        self.__active = False\n",
    "        Golem.__population -= 1\n",
    "    \n",
    "    def is_active(self):\n",
    "        if datetime.date.today().year - self.built_year >= Golem.__life_span:\n",
    "            self.cease\n",
    "        return self.__active\n",
    "    \n",
    "    def population(self):\n",
    "        return Golem.__population\n",
    "\n",
    "g = Golem('Clay')\n",
    "g.population\n",
    "g.population()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果，你希望外部能够像获得 Class 的属性那样，直接写 `g.population`，而不是必须加上一个括号 `g.population()` 传递参数（实际上传递了一个隐含的 `self` 参数），那么可以在 `def population(self):` 之前的一行加上一句 `@property`：\n",
    "\n",
    "```python\n",
    "class Golem:\n",
    "    __population = 0\n",
    "    ...\n",
    "    \n",
    "    @property\n",
    "    def population(self):\n",
    "        return Golem.__population\n",
    "```\n",
    "\n",
    "如此这般之后，你就可以用 `g.population` 了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    __population = 0\n",
    "    __life_span = 10\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "        self.__active = True\n",
    "        Golem.__population += 1\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "    \n",
    "    def cease(self):\n",
    "        self.__active = False\n",
    "        Golem.__population -= 1\n",
    "    \n",
    "    def is_active(self):\n",
    "        if datetime.date.today().year - self.built_year >= Golem.__life_span:\n",
    "            self.cease\n",
    "        return self.__active\n",
    "    \n",
    "    @property\n",
    "    def population(self):\n",
    "        return Golem.__population\n",
    "\n",
    "g = Golem('Clay')\n",
    "g.population\n",
    "# g.population = 100"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如此这般之后，不仅你可以直接引用 `g.population`，并且，在外部不能再直接给 `g.population` 赋值了，否则会报错：\n",
    "\n",
    "```python\n",
    "---------------------------------------------------------------------------\n",
    "AttributeError                            Traceback (most recent call last)\n",
    "<ipython-input-16-5d8c475304d3> in <module>\n",
    "     26 g = Golem('Clay')\n",
    "     27 g.population\n",
    "---> 28 g.population = 100\n",
    "\n",
    "AttributeError: can't set attribute\n",
    "```\n",
    "\n",
    "到此为止，Encapsulation 就做得不错了。\n",
    "\n",
    "如果你非得希望从外部可以设置这个值，那么，你就得再写个函数，并且在函数之前加上一句：\n",
    "```python\n",
    "    ...\n",
    "    \n",
    "    @property\n",
    "    def population(self):\n",
    "        return Golem.__population\n",
    "    \n",
    "    @population.setter\n",
    "    def population(self, value):\n",
    "        Golem.__population = value\n",
    "\n",
    "```\n",
    "\n",
    "这样之后，`.population` 这个 Attribute 就可以从外部被设定其值了（虽然在当前的例子中显得没必要让外部设定 `__population` 这个值…… 以下仅仅是为了举例）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "101"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "101"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Golem in module __main__:\n",
      "\n",
      "class Golem(builtins.object)\n",
      " |  Golem(name=None)\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, name=None)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  cease(self)\n",
      " |  \n",
      " |  is_active(self)\n",
      " |  \n",
      " |  say_hi(self)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  population\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '_Golem__population': 101,\n",
       "              '_Golem__life_span': 10,\n",
       "              '__init__': <function __main__.Golem.__init__(self, name=None)>,\n",
       "              'say_hi': <function __main__.Golem.say_hi(self)>,\n",
       "              'cease': <function __main__.Golem.cease(self)>,\n",
       "              'is_active': <function __main__.Golem.is_active(self)>,\n",
       "              'population': <property at 0x1068f9d68>,\n",
       "              '__dict__': <attribute '__dict__' of 'Golem' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Golem' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "{'name': 'Clay', 'built_year': 2019, '_Golem__active': True}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "<property at 0x1068f9d68>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "10000"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "import datetime\n",
    "\n",
    "class Golem:\n",
    "    __population = 0\n",
    "    __life_span = 10\n",
    "    \n",
    "    def __init__(self, name=None):\n",
    "        self.name = name\n",
    "        self.built_year = datetime.date.today().year\n",
    "        self.__active = True\n",
    "        Golem.__population += 1\n",
    "    \n",
    "    def say_hi(self):\n",
    "        print('Hi!')\n",
    "    \n",
    "    def cease(self):\n",
    "        self.__active = False\n",
    "        Golem.__population -= 1\n",
    "    \n",
    "    def is_active(self):\n",
    "        if datetime.date.today().year - self.built_year >= Golem.__life_span:\n",
    "            self.cease\n",
    "        return self.__active\n",
    "    \n",
    "    @property\n",
    "    def population(self):\n",
    "        return Golem.__population\n",
    "    \n",
    "    @population.setter\n",
    "    def population(self, value):\n",
    "        Golem.__population = value\n",
    "\n",
    "g = Golem('Clay')\n",
    "g.population\n",
    "g.population = 100\n",
    "ga = Golem('New')\n",
    "g.population\n",
    "ga.population\n",
    "help(Golem)\n",
    "Golem.__dict__\n",
    "g.__dict__\n",
    "hasattr(Golem, 'population')\n",
    "getattr(Golem, 'population')\n",
    "setattr(Golem, 'population', 10000)\n",
    "g.population    # 所以，在很多的情况下，不把数据封装在 Class 内部的话，后面会有很多麻烦。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  },
  "toc-autonumbering": true
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
